Automaty komórkowe 1.0 - Dokumentacja

Spis treści

Wstęp

Co to jest?

Automaty komórkowe 1.0 to rozbudowany program do symulacji komputerowej automatów komórkowych, a to jest jego dokumentacja. Dokumentacja to nie jest dobre miejsce na wprowadzenie do tej fascynującej dziedziny, więc jeśli nie wiesz jeszcze, co to takiego, poszukaj czegoś na ten temat.

Program powstał do celów prezentacyjnych, jako dodatek do mojego referatu pt. "Automaty komórkowe i ich zastosowania", który napisałem na II Ogólnopolską Konferencję Twórców Gier Komputerowych, która odbyła się w dniach 1-3 kwietnia 2005 roku w Akademii Podlaskiej w Siedlcach.

Umożliwia on implementowanie różnego rodzaju automatów komórkowych poprzez pisanie kodu przeliczającego w języku skryptowym, a komórki mogą być wizualizowane na różne sposoby. Oprócz przeglądania dołączonych do niego, przykładowych skryptów umożliwia on więc także własną zabawę z automatami komórkowymi. Dlatego zdecydowałem się go udostępnić.

Jedno trzeba przyznać - program nie grzeszy szybkością działania. W niektórych przypadkach nie pozwala na wykonywanie więcej, niż kilku, kilkunastu czy kilkudziesięciu iteracji na sekundę. (w innych zaś można wykonywać ich nawet 100'000). Niestety, taka jest cena, jaką płaci się za elastyczność. Interpretacja języka skryptowego nigdy nie będzie tak szybka, jak wykonywanie prawdziwego kodu.

O programie

Wymagania

  1. Microsoft Windows
  2. Microsoft DirectX 9.0c
  3. Jakiś sensowny procesor
  4. Jakaś sensowna karta graficzna

Użyte zostały

Do napisania programu zostały użyte:

Biblioteka multimedialna Microsoft DirectX 9.0c
Język skryptowy AngelCode Spripting Library (AngelScript) 1.10.1c
Biblioteka do obsługi formatów graficznych DevIL 1.6.5
Biblioteka do kompresji danych zlib 1.2.1
Moja biblioteka uniwersalna i parser XNL pxlib 2.3.2
Mój szkielet Engine 3

Licencja

Program rozpowszechniany jest na licencji GNU GPL. Oznacza m.in., że:

  1. Możesz go swobodnie rozpowszechniać i używać za darmo
  2. Masz dostęp do kodu źródłowego
  3. Musisz poszanować prawa autorskie

Kwestia źródeł jest o tyle istotna, że wraz z programem po raz pierwszy udostępniłem mój Engine 3 - rozbudowany szkielet aplikacji DirectX i inne moduły, które są dla mnie (w chwili pisania tych słów) dużym osiągnięciem programistycznym. Większość kodu jest dobrze udokumentowana w formacie Doxygen. Należą do niego pliki:

Base Moduł bazowy z różnymi podstawowymi elementami
Error Moduł do obsługi błędów
Framework Szkielet aplikacji DirectX
Gfx Moduł do wyświetlania grafiki 2D (w tym dość potężny Font Manager)
GUI Moduł bazowy graficznego interfejsu użytkownika
Stream Moduł z moją realizacją strumieni danych binarnych

Tak więc masz dostęp do kompletnego źródła Engine 3 jako do części tego programu na licencji GNU GPL. Możesz je przeglądać i swobodnie uczyć się z nich. Jeśli chcesz zastosować te moduły czy inne większe fragmenty jego kodu w swoim projekcie, obowiązują cię ograniczenia zasady copyleft. Możesz to robić do celów edukacyjnych. Jeśli jednak chciałbyś ukończyć i opublikować swoją produkcję i używasz w niej choćby fragmentu moich źródeł, cała twoja produkcja musi być również wolnym oprogramowaniem (na licencji GNU lub innej kompatybilnej).

Od autora

Automatami komórkowymi interesuję się po trochu od lat zbierając różne artykuły i inne materiały. Napisałem kiedyś wygaszacz ekranu z nimi (ProgrameX Cellular Automat ScreenSaver), a teraz mam zamiar zastosować je do symulacji wiatru w mojej grze RTS - SnowCraft 2.

Poprzednia wersja mojego artykułu na ten temat ma już kilka lat i teraz wiem na ten temat dużo więcej, niż wiedziałem wtedy. Dlatego mam nadzieję, że referat, do którego napisałem ten program, dobrze go zastąpi.

Program miał być tylko dodatkiem do referatu. Bardzo szybko jednak postanowiłem zaprojektować i napisać go tak rozbudowanym, by nadawał się do opublikowania jako osobna produkcja. Decyzja o tym, jak również o upublicznieniu jego źródeł (które, nawiasem mówiąc, skupione w jednym pliku main.cpp nie są najwyższej jakości :) nie była łatwa.

Mam jednak nadzieję, że dzięki niej przynajmniej kilka osób zainteresuje się automatami komórkowymi, nauczy się niełatwej niestety obsługi tego programu, pobawi się nim trochę i być może kiedyś nawet zastosuje automaty komórkowe w swoich grach.

Adam Sawicki "Regedit"
Częstochowa, 2005-02-11

Kontakt

Strona domowa http://www.regedit.risp.pl
ProgrameX http://www.programex.prv.pl
E-mail regedit_USUN_@risp.pl (usuń to _USUN_ :)
IRC #programex i #warsztat w sieci PolNet
Gadu-Gadu 2272474

Skróty klawiszowe

[ESC]Zatrzymaj
[Shift]+[ESC]Wyzeruj licznik iteracji
[Spacja]Wykonaj jeden krok
[Enter]Uruchom
[Ctrl]+[Enter]Przełącz na pełny ekran lub z powrotem do okna
[Ctrl]+[O]Wczytaj tablicę
[Ctrl]+[S]Zapisz tablicę
[Ctrl]+[Shift]+[O]Wczytaj obrazek
[Ctrl]+[Shift]+[S]Zapisz obrazek
[1]..[0]Przyciski użytkownika
[F]Filtruj teksturę
[T]Rozłóż teksturę sąsiadująco
[+], [-]Zmień powiększenie
[*]Zresetuj powiększenie
[lewo], [prawo]Zmień prędkość
[góra], [dół]Wybierz rozdzielczość

Działanie programu

Uruchamianie programu

Program działa w oparciu o pliki AK. Bez takiego pliku nie da się go uruchomić. Program można uruchamiać na dwa sposoby:

Jeśli wybrany plik AK nie będzie poprawny lub wystąpi inny błąd podczas uruchamiania albo działania:

  1. program utworzy plik Error.txt,
  2. zapisze do niego szczegółowe informacje o zaistniałym błędzie,
  3. otworzy go w systemowym Notatniku,
  4. po czym natychmiast zakończy działanie.

Program wykorzystuje DirectX i przez cały czas nieustannie pętli się wykorzystując 100% mocy procesora. To nic strasznego - tak robią wszystkie gry. Okno programu można swobodnie rozszerzać, maksymalizować i minimalizować. Kiedy zminimalizowany, program przestaje się pętlić i zatrzymuje swoje działanie. Dostępny jest też tryb pełnoekranowy. Program poprawnie powraca po wyjściu w takim trybie do systemu kombinacją klawiszy [Alt]+[Tab] czy [Ctrl]+[ESC].

Wykonywanie programu

Podczas działania pracą programu można sterować za pomocą kontrolek umieszczonych na pasku w górnej części.

Uruchamianie i zatrzymywanie

Do uruchomienia obliczeń służy przycisk Uruchom, a do zatrzymania Zatrzymaj.

Running

Opcjonalna
Typ: logiczny
Domyślnie: false

Ta sekcja pierwszej części pliku AK określa, czy obliczenia mają być uruchomione automatycznie natychmiast po uruchomieniu programu i wczytaniu pliku AK, czy użytkownik będzie musiał za każdym razem uruchomić ten proces ręcznie (dokonując wcześniej ewentualnych przygotowań).

Przykład:

Running = true

Regulacja prędkości

FPS to liczba klatek na sekundę (ang. "frames per second"). Obrazuje ona szybkość pracy programu. Na każdą klatkę składają się obliczenia związane z automatem komórkowym oraz odrysowanie zawartości okna. Liczba klatek na sekundę jest zależna od szybkości procesora, karty graficznej, a szczególnie od aktualnego obciążenia programu obliczeniami. Jeśli FPS jest małe, program chodzi bardziej skokowo i jego interfejs nieco wolniej reaguje na polecenia.

IPS to liczba iteracji na sekundę. Obrazuje szybkość wykonywania obliczeń na automacie komórkowym. Można ją zmieniać za pomocą suwaka Zmień prędkość w skali logarytmicznej w zakresie od 1 iteracji na 10 sekund aż do 100'000 iteracji na sekundę. Im większa prędkość, tym mniej FPS. Na suwaku tym zaznaczone są kolorami pewne wartości:

Obszar żółty to obszar powyżej liczby klatek na sekundę (FPS). Ustawienie wartości w tym przedziale nie robi w zasadzie żadnej różnicy względem obszaru zielonego oprócz tego, że program będzie wykonywał więcej obliczeń, niż odrysowań na sekundę. Nie będziesz mógł wtedy zobaczyć na ekranie wyników obliczeń każdej iteracji.

Obszar czerwony to limit ilości iteracji na sekundę, powyżej którego komputer nie nadąża. Na początku jest nieznany. Dopiero jego przekroczenie powoduje zadziałanie zabezpieczenia i ustalenie tej wartości. Zabezpieczenie to wykrywa sytuację, w której program ma do wykonania więcej iteracji na sekundę, niż jest w stanie wykonać. W każdej klatce działania programu zbiera się wtedy coraz więcej iteracji do wykonania i gdyby zabezpieczenie nie zadziałało, program w końcu całkowicie by się zawiesił. Dzięki niemu wartość IPS zostaje już po kilku klatkach ustawiona na bezpieczną wartość bliską maksymalnej.

Limit IPS zależy od szybkości procesora, ale przede wszystkim od algorytmu przeliczania automatu komórkowego zawartego pliku AK. Jeśli w każdej iteracji przeliczane są wszystkie komórki, maksymalna wartość IPS może wynosić kilkadziesiąt. Jeśli przeliczana jest tylko jedna, program może być w stanie wykonać nawet pełne 100'000 iteracji na sekundę.

Speed

Opcjonalna
Typ: liczba rzeczywista
Domyślnie: 1.0

Ta sekcja określa początkową prędkość aktualizacji w iteracjach na sekundę (IPS), jaka będzie ustawiona w programie po uruchomieniu i wczytaniu danego pliku AK.

Przykład:

Speed = 10.0

Praca krokowa

Możesz też wykonywać automat komórkowy krok po kroku używając polecenia Wykonaj jeden krok.

Ostatnia z liczb wyświetlanych na panelu górnym to licznik iteracji. Do jego wyzerowania służy przycisk Wyzeruj licznik iteracji.

Przycisk Wykonaj daną liczbę iteracji pozwala z kolei na wykonanie określonej ilości obliczeń. Kolejne iteracje wykonywane są w maksymalnym możliwym tempie, a efekty obliczeń są często wyświetlane na ekranie w postaci kolejnych klatek.

Plik AK

Podstawą działania programu jest wykonywanie podanego podczas uruchomienia pliku AK. Przygotowanie takiego pliku jest z kolei podstawą całej zabawy z programem, jeśli tylko nie będzie się ona ograniczała wyłącznie do przeglądania plików przygotowanych przez innych.

Plik AK to plik tekstowy. Znaki końca wiersza mogą być kodowane w dowolny sposób, ale domyślne zaleca się windowsowe CR+LF. Kodowanie znaków powinno być jednobajtowe. W zakresie znaków ANSI kompatybilne są ze sobą różne kodowania, w tym Windows-1250, ISO-8859-1, ISO-8859-2 i UTF-8. Jeśli zachodzi potrzeba kodowania polskich liter, należy użyć kodowania Windows-1250 (CP1250).

Plik AK składa się z dwóch osobnych części oddzielonych napisem:

--CODE--
Część IJęzyk opisu XNLOpisuje parametry automatu komórkowego
Część IIJęzyk skryptowy AngelScriptZawiera kod do wykonania przez program

W językach używanych w obydwu tych częściach obowiązuje wolny format zapisu i rozróżniana jest wielkość liter.

Część I

Pierwsza część pliku AK opisuje parametry automatu komórkowego. Jest w języku XNL. XNL (Extensible Description Language) to język opisu opracowany przeze mnie w wyniku niezadowolenia z popularnego standardu XML. Parser do niego wchodzi w skład mojej biblioteki pxlib dostępnej na licencji GNU LGPL. Pełną specyfikację języka możesz naleźć na stronie pxxnl. Tutaj przedstawię uproszczone podstawy jego składni.

Podstawy XNL

Podstawą XNL są elementy, czyli napisy oddzielane dowolnym odstępem (spacjami, tabulacjami, końcami wiersza), przecinkiem , lub średnikiem ;. Jeśli element zawiera spacje lub inne znaki specjalne, należy go objąć w apostrofy '' lub cudzysłowy "".

Do takich znaków specjalnych należą separatory atrybutów - znak równości = lub dwukropek :. Specjalne znaczenie mają też nawiasy kwadratowe [] i klamrowe {}, które wyznaczają początek i koniec sekcji.

Jak widać, każdy z tych rodzajów tokenów w języku XNL może być zapisywany na conajmniej dwa sposoby. Choć w rzeczywistości istnieje możliwość ich rozróżniania, opisywany program tego nie robi. Można więc używać ich zamiennie.

Komentarze wstawia się tak:

[/
  Komentarz...
/]

Składnia części I

Pierwsza część pliku AK musi rozpoczynać się takim nagłówkiem:

[Automat komorkowy 1]

Dalej następuje zbiór sekcji w postaci:

<nazwa> = <wartość>

Jeśli jakaś sekcja występuje więcej niż raz, efekt jest niezdefiniowany (ang. UB - Undefined Behaviour). Kiedy coś jest niezdefiniowane, nie można tak robić, bo może się stać wszystko - łącznie z eksplozją Księżyca bądź śmiercią programisty :)

Opisy dopuszczalnych sekcji, ich nazwy, składnia wartości i inne dane są rozsiane po tym dokumencie. Ich pełną listę znajdziesz w Indeksie sekcji części I pliku AK. Tutaj przedstawiam jedną z nich:

Title

Obowiązkowa
Typ: łańcuch

Określa tytuł automatu komórkowego

Przykład:

Title = "Mrówka Lagtona"

Część II

Część II pliku AK to kod w języku skryptowym AngelScript. Ten kod zostaje wczytany podczas uruchamiania programu, jest kompilowany do specjalnego kodu binarnego, który program wykonuje potem w wirtualnej maszynie. Pełną dokumentację języka AngelScript znajdziesz na stronie Script Writer's manual. Tutaj przedstawię tylko podstawy jego składni.

AngelScript ma składnię wzorowaną na języku programowania C++. Dostępne są więc wszelkie instrukcje sterujące, jak pętle for, while i do...while, a także if, switch itp. Tak samo, jak w C++ definiuje się też zmienne globalne, zmienne lokalne i funkcje.

Język ten nie jest zorientowany obiektowo. W skrypcie nie ma więc możliwości tworzenia własnych klas. Nie ma też wskaźników ani łańcuchów znaków. Warto natomiast stosować przekazywanie parametrów przez wartość stałą - pomoże to kompilatorowi zoptymalizować kod:

void MojaFunkcja(const int Parametr1, const bool Parametr2)
{
  ...
}

Komentarze pisze się w tym jezyku tak samo, jak w C++:

// Komentarz jednolinijkowy
/*
  Komentarz wielolinijkowy
*/

Dalsze szczegóły na temat skryptów są tematem całego następnego rozdziału.

Skrypty

API

Program oddaje do dyspozycji dla skryptu wiele funkcji służących do różnych operacji (np. matematycznych) oraz do komunikacji z przetwarzanym automatem komórkowym. Ze swej strony skrypt powinien udostępniać dla programu pewne funkcje, które program będzie wywoływał. Jedne z nich są wymagane, inne opcjonalne. Funkcje identyfikowane są po pełnym nagłówku (nazwa z rozróżnieniem wielkości liter, ilość i typy parametrów oraz typ wartości zwracanej).

Opisy jednych i drugich funkcji są porozrzucane po całej tej dokumentacji. Ich pełną listę znajdziesz w Indeksie funkcji części II pliku AK. Tutaj przedstawiam dwie podstawowe:

void OnInit()

Obowiązkowo udostępniana przez skrypt dla programu

Wywoływana jest jednokrotnie podczas uruchamiania programu, po wczytaniu pliku AK i zainicjowaniu wszelkich struktur danych. Może posłużyć do inicjalizacji skryptu - wstępnego wypełnienia tablicy i innych przygotowań.

Przykład:

void OnInit()
{
  int x, y, w = GetWidth(), h = GetHeight();
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      SetBuffer_b1(x, y, false);
    }
  }
}

void OnFrame()

Obowiązkowo udostępniana przez skrypt dla programu

Wywoływana jest w każdej iteracji. Jej zadaniem jest przeprowadzanie właściwych obliczeń na tablicy komórek.

Przykład:

void OnFrame()
{
  int x, y, w = GetWidth(), h = GetHeight();
  uint count;
  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      count = 0;
      if (GetBuffer_b1(x-1, y-1)) count++;
      if (GetBuffer_b1(x  , y-1)) count++;
      if (GetBuffer_b1(x+1, y-1)) count++;
      if (GetBuffer_b1(x-1, y  )) count++;
      if (GetBuffer_b1(x+1, y  )) count++;
      if (GetBuffer_b1(x-1, y+1)) count++;
      if (GetBuffer_b1(x  , y+1)) count++;
      if (GetBuffer_b1(x+1, y+1)) count++;

      if (count > 4)
        SetBuffer_b1(x, y, true);
      else if (count < 4)
        SetBuffer_b1(x, y, false);
    }
  }
}

Dane

Podstawą działania skryptu jest oczywiście przetwarzanie pewnych danych. Dlatego najpierw trzeba poznać dostępne typy danych i możliwe operacje na nich.

Typy

W przeciwieństwie do większości języków skryptowych, w AngelScript zmienne trzeba jawnie deklarować i jawnie podawać ich typ, tak jak w C++ i innych normalnych językach programowania. Dostępne typy danych odpowiadają typom języka C++. Oto niektóre z nich:

Typ AngelScriptOpisPrzykładowe wartościOdpowiedniki w C++
boolTyp logicznyfalse truebool
int8Liczba 1-bajtowa ze znakiem-128, 0, +33, 127char
int16Liczba 2-bajtowa ze znakiem-32768, 0, 32767, 0x02short
int32, intLiczba 4-bajtowa ze znakiem-2147483648, +33, 2147483647, 0x02int, long
uint8Liczba 1-bajtowa bez znaku0, 33, 255, 0xFFunsigned char, BYTE
uint16Liczba 2-bajtowa bez znaku0, 65535, 0xFFFFunsigned short, WORD
uint32, uintLiczba 4-bajtowa bez znaku33, 4294967295, 0xDEADCODEunsigned, DWORD
floatLiczba zmiennoprzecinkowa-10, 0.05, -2.5e-10float
bits81-bajtowy zbiór bitów 
bits162-bajtowy zbiór bitów 
bits324-bajtowy zbiór bitów 

Zmienne globalne

W celu przechowywania w skrypcie wartości pomiędzy wywołaniami OnInit i OnFrame (pomiędzy kolejnymi iteracjami) można użyć zmiennych globalnych. Można też podczas definiowania nadawać im określoną wartość początkową:

int Licznik = 0;

void OnFrame()
{
  // ...
  Licznik++;
}

Tablice

AngelScript nie jest zbyt bogaty, jeśli chodzi o wbudowane struktury danych. Wynika to z jego założeń, elastyczności i dążenia do maksymalnej wydajności. Nie ma struktur dynamicznych (jak listy). Nie ma też struktur tak uniwersalnych i wygodnych w użyciu, jak znane choćby z PHP tablice asocjacyjne. Jedynym wbudowanym kontenerem jest zwykła, statyczna tablica o podanym rozmiarze. Oto przykład jej użycia:

int[] tab(4);

tab[0] = 1;
tab[1] = 2;
tab[2] = 4;
tab[3] = tab[0] * tab[1];

Rozmiar tablicy nie musi być znany w momencie kompilacji. Jako podany w nawiasie w miejscu definicji tablicy rozmiar (tu 4) można użyć zmiennej lub wyrażenia.

Operatory

Dostępne są znane z C++ operatory arytmetyczne + - * / %, a także logiczne || && ! i bitowe & | ~ ^.

Podczas dzielenia zasada jest taka sama, jak w C++. Dzielenie dwóch liczb całkowitych to dzielenie całkowite - z obcięciem reszty. Jeśli choć jedna z liczb jest zmiennoprzecinkowa, dokonywane jest dzielenie zmiennoprzecinkowe. Dzielenie przez zero jest wykrywane przez program i powoduje jego awaryjne zakończenie z błędem.

W AngelScript bardzo użyteczne bywa rzutowanie typów. Jego składnia to typ(wyrażenie).

Stałe

Do dyspozycji skryptu oddane są dwie podstawowe stałe matematyczne w postaci funkcji zwracających stosowne wartości:

float PI()
float E()

Zwracają odpowiednio wartość stałej PI równej mniej więcej 3.14 oraz E równej mniej więcej 2.71.

Funkcje matematyczne

Sam AngelScript nie ma wbudowanych żadnych funkcji, nawet podstawowych operacji matematycznych. Interpreter wbudowany w program wyposażyłem we wszystkie podstawowe funkcje tego rodzaju, jednak operujące tylko na liczbach zmiennoprzecinkowych pojedynczej precyzji.

Typ zmiennoprzecinkowy o podwójnej precyzji - double - jest również dostępny w języku. Nie zawarłem go na liście typów, ponieważ nie oferuję dla niego wsparcia w postaci takich właśnie funkcji.

int abs_i(int)
float abs_f(float)

Zwraca wartość bezwzględną podanej liczby.

uint min_u(uint, uint)
int min_i(int, int)
float min_f(float, float)

Zwraca mniejszą z podanych wartości.

uint max_u(uint, uint)
int max_i(int, int)
float max_f(float, float)

Zwraca większą z podanych wartości.

uint minmax_u(uint, uint, uint)
int minmax_i(int, int, int)
float minmax_f(float, float, float)

Zwraca wartość drugą ograniczoną do przedziału od pierwszej do trzeciej.

bool rand_b()

Zwraca losową wartość logiczną. Współczynnik wypełnienia to 50%.

uint rand_u()

Zwraca losową spośród wszystkich możliwych wartości liczby typu uint. Rozkład równomierny.

float rand_f()

Zwraca losową wartość zmiennoprzecinkową z przedziału od 0.0 do 1.0. Rozkład równomierny.

float rand_normal(float)

Zwraca wartość losową o rozkładzie normalnym, wartości oczekiwanej 0.0 i podanym odchyleniu standardowym (sigma).

uint rand_u_range(uint, uint)
float rand_f_range(float, float)

Zwraca losową wartość z podanego zakresu. Rozkład równomierny.

float sin(float)
float cos(float)
float tan(float)

Funkcje trygonometryczne. Kąt podaje się w radianach.

float asin(float)
float acos(float)
float atan(float)

Funkcje odwrotne do trygonometrycznych (arcus). Kąt jest w radianach.

float sinh(float)
float cosh(float)
float tanh(float)

Funkcje hiperboliczne. Kąt podaje się w radianach.

float atan2(float, float)

Zwraca kąt wektora o podanych współrzędnych x i y. Odpowiada atan(y/x), ale nie występuje w nim dzielenie przez zero. Kąt jest w radianach.

float ceil(float)

Zaokrągla podaną liczbę w górę.

float floor(float)

Zaokrągla podaną liczbę w dół.

int round(float)

Zaokrągla podaną liczbę do najbliższej liczby całkowitej.

Konwersję do liczby całkowitej bez zaokrąglania (z zaokrąglaniem w dół) przeprowadza się za pomocą zwykłego rzutowania wartości typu zmiennoprzecinkowego (rzeczywistego) na typ całkowity.

float fmod(float, float)

Reszta z dzielenia zmiennoprzecinkowego.

float log10(float)
float log(float)
float exp(float)
float pow(float, float)
float sqrt(float)

Logarytm dziesiętny, logarytm naturalny, funkcja wykładnicza (e do potęgi), potęga i pierwiastek kwadratowy.

Błędy

Błędy niestety są nieuniknione i dlatego warto poświęcić im trochę miejsca. Po wystąpieniu błędu w programie (w tym błędu podczas wczytywania, kompilowania czy wykonywania skryptu) program natychmiast kończy działanie i wyświetla treść błędu w pliku tekstowym. Jeśli skrypt się zapętli, program się zawiesi.

W pokazywanym po wystąpieniu błędu komunikacie numer linii skryptu odnosi się niestety do numeru linii względem początku drugiej części pliku AK (tej ze skryptem), a nie względem początku całego pliku AK. Niestety też, błąd kompilacji skryptu często nie zawiera treści i można się tylko domyślać (albo próbować komentować różne części kodu), w którym miejscu się znajduje i co jest źle.

Do dyspozycji skryptu program oddaje kilka funkcji związanych tematycznie z obsługą błędów:

void Beep(uint, uint)

Wydobywa z systemowego głośniczka (PC Speakera) dźwięk o podanej częstotliwości (w Hz) i czasie trwania (w ms). Na czas odgrywania dźwięku cały program zawiesza działanie, dlatego czas ten nie może być długi, a tej funkcji należy używać wyłącznie do celów debugowania.

Przykład:

Beep(1000, 100);

void Debug(int, int)

Wyświetla na pasku podpowiedzi w oknie programu podane dwie liczby całkowite. Będą tam widoczne do czasu ich zastąpienia przez nowy napis w wyniku ponownego wywołania tej funkcji, wyświetlenia podpowiedzi do kontrolki pod kursorem myszy itp.

void error(int)

Wywołuje błąd. Kończy działanie programu pozwalając przekazać kod błędu jako liczbę całkowitą.

Przykład:

if (count <= 0)
  error(13);

void assert(bool)

Wywołuje błąd, jeśli podana w nawiasie wartość logiczna albo wyrażenie nie jest prawdziwe. Służy do sprawdzania asercji, czyli warunków, które bezwzględnie powinny być spełnione w danym miejscu.

Przykład:

assert(count > 0);

Komórki

Składniki komórki

Podstawą działania automatu komórkowego są oczywiście komórki. Strukturą danych, na której się operuje jest 2-wymiarowa tablica takich komórek. W celu zapewnienia dużej elastyczności, a zarazem dostatecznej wydajności nie ma możliwości definiowania własnego formatu komórki. W zamian za to, każda komórka składa się z wielu pól różnego rodzaju, które są do dyspozycji i mogą być dowolnie wykorzystywane. Są to:

Po uruchomieniu programu, a, przed wywołaniem funkcji OnInit() ze skryptu cała tablica inicjowana jest zerami:

W razie potrzeby można dokonać własnego wypełnienia wstępnego w funkcji OnInit().

Szerokość i wysokość

Tablica komórek jest 2-wymiarowa, a jej szerokość i wysokość nie może ulegać zmianom w czasie działania programu. Te wymiary podaje się w pierwszej części pliku AK w sekcjach:

Width, Height

Obowiązkowe
Typ: liczba całkowita (większa od zera, rozsądnie mała)

Określają szerokość i wysokość tablicy komórek.

Przykład:

Width = 100
Height = 75

Dla skryptu dostępne są funkcje do odczytywania szerokości i wysokości tablicy. Warto ich używać zamiast wpisywać te wymiary do skryptu na sztywno powielając je względem określenia szerokości i wysokości w części I pliku AK. Dzięki temu w przypadku zmiany tych parametrów wystarczy je zmienić tylko tam.

int GetWidth()
int GetHeight()

Zwracają odpowiednio szerokość i wysokość tablicy w komórkach.

Wartości poza zakresem

Komórki w tablicy indeksowane są od współrzędnych (x,y) równych (0,0) w prawo i w dół. Ostatnia komórka - ta po prawej na dole - ma więc współrzędne (GetWidth()-1, GetHeight()-1). Zapisywanie do komórek poza zakresem powoduje błąd.

Dla uproszczenia pisania skryptów gwarantowane jest natomiast, że odczytywanie komórek z dowolnych współrzędnych, także spoza zakresu, jest zawsze poprawne. Zwracana wartość spoza zakresu jest brana z tablicy po zawinięciu jej (można to sobie wyobrazić jako sąsiadujące rozłożenie tablicy w poziomie i w pionie powtórzonej na całej powierzchni współrzędnych nieskończenie wiele razy) lub brana jest pewna wartość domyślna, zależna od przedstawionych niżej ustawień.

Współrzędne są typu int, a więc typu całkowitego ze znakiem. Dzięki temu mogą być też ujemne.

Wrap

Obowiązkowa
Typ: logiczny

  • true - podczas odczytywania komórek spoza zakresu zwracane są wartości z tablicy po zawinięciu (powtórzeniu)
  • false - podczas odczytywania komórek spoza zakresu zwracana jest wartość domyślna

Przykład:

Wrap = true

Default

Opcjonalna
Musi wystąpić poniżej Wrap
Może wystąpić tylko, jeśli Wrap = false

Określa wartość domyślną komórek, używaną podczas odczytywania komórek spoza zakresu tablicy.

Struktura: sekcja [], a w niej dowolne spośród składowych komórki i ich wartości odpowiedniego typu, w dowolnej kolejności.

Domyślnie:

b1,b2,b3,b4 = false
u1,u2,u3,u4 = 0
f1,f2,f3,f4 = 0.0

Przykład:

Default = [
  b1 = false
  f1 = 0.02
  u3 = 0
  u4 = 10
]

Bufory

Tablicę komórek nazywamy buforem. Program może stosować buforowanie pojedyncze, podwójne lub potrójne.

Buffers

Obowiązkowa
Wartość: 1, 2, 3

Określa rodzaj buforowania - ilu buforów ma używać program.

Przykład:

Buffers = 2

Buforowanie pojedyncze polega na tym, że program przechowuje tylko jedną kopię tablicy komórek. Wszelkie operacje odczytywania i zapisywania są przeprowadzana bezpośrednio na niej. Programowany z użyciem pojedynczego buforowania automat komórkowy ma to do siebie, że przeliczając kolejne komórki i korzystając przy tym z wartości komórek sąsiednich odczytuje się te zmienione już wartości.

Buforowanie podwójne polega na tym, że program przechowuje dwie kopie tablicy. Zapis odbywa się do bufora pierwszego, a odczyt z bufora drugiego. Dzięki temu podczas przeliczania odczytywane są wartości komórek sąsiednich jeszcze sprzed zmodyfikowania w danej iteracji.

Program sam zarządza buforami tak, że po każdej iteracji jej wyniki z bufora pierwszego są kopiowane do drugiego i będą odczytywane jako "stare" wartości podczas przeliczania w iteracji następnej.

Nie trzeba zapisywać każdej komórki tablicy w każdej nowej iteracji. Komórki tablicy pierwszej (tej do zapisywania) mają na początku każdej iteracji te same wartości, jak komórki tablicy drugiej (tej, z której się odczytuje). Dzięki temu niezapisanie pewnej komórki po prostu pozostawia jej wartość bez zmian.

Buforowanie potrójne działa tak jak podwójne, ale program przechowuje dodatkowo kopię tablicy z wynikami obliczeń poprzedniej iteracji. Nie ma dostępu do tych wartości. Mogą one posłużyć jedynie do wizualizacji pól typu bool - do zaznaczenia na inny kolor komórek, które były i są aktywne, były i są nieaktywne, a na inny tych, które stały się lub przestały być aktywne względem poprzedniej iteracji.

Wszystko to odbywa się w sposób przezroczysty. Program sam zarządza buforami. W skrypcie używa się buforów niezależnie od wybranego trybu buforowania.

void SetBuffer_b1(int, int, bool)
void SetBuffer_b2(int, int, bool)
void SetBuffer_b3(int, int, bool)
void SetBuffer_b4(int, int, bool)
void SetBuffer_u1(int, int, uint)
void SetBuffer_u2(int, int, uint)
void SetBuffer_u3(int, int, uint)
void SetBuffer_u4(int, int, uint)
void SetBuffer_f1(int, int, float)
void SetBuffer_f2(int, int, float)
void SetBuffer_f3(int, int, float)
void SetBuffer_f4(int, int, float)

bool GetBuffer_b1(int, int)
bool GetBuffer_b2(int, int)
bool GetBuffer_b3(int, int)
bool GetBuffer_b4(int, int)
uint GetBuffer_u1(int, int)
uint GetBuffer_u2(int, int)
uint GetBuffer_u3(int, int)
uint GetBuffer_u4(int, int)
float GetBuffer_f1(int, int)
float GetBuffer_f2(int, int)
float GetBuffer_f3(int, int)
float GetBuffer_f4(int, int)

Funkcje do zapisywania i odczytywania poszczególnych składowych komórek tablicy (bufora) o podanych współrzędnych.

Zapisywanie i odczytywanie stanu

Stan tablicy może zostać zapisany do pliku lub wczytany z pliku. Odbywa się to w sposób niezależny od cyklu przeliczeń i skrypt nie ma z tym niczego wspólnego. Do wczytania tablicy z pliku służy polecenie Wczytaj tablicę, a do zapisania tablicy do pliku - Zapisz tablicę. Polecenia ta działają wyłącznie w trybie okienkowym (nie na pełnym ekranie). Pokazują standardowe okna dialogowe Windows do wyboru nazwy pliku i pozwalają wybrać jeden z dwóch formatów pliku:

Jeśli wymiary tablicy zapamiętanej we wczytywanym pliku nie odpowiadają wymiarom tablicy automatu wykonywanego aktualnie w programie, wartości komórek spoza zakresu zostaną zignorowane, a komórki niewczytane z pliku pozostaną bez zmian.

Format pliku AKN

To plik binarny przechowujący zapamiętaną tablicę komórek w postaci nieskopresowanej. Rozpoczyna się od nagłówka w postaci 4 znaków:

AKN1

Dalej następuje szerokość i wysokość w postaci dwóch liczb całkowitych 32-bitowych.

Dalej ciągnie się tablica komórek. Ilość komórek to szerokość * wysokość, a każda z nich zajmuje 36 bajtów i składa się z takich pól:

  1. 4 1-bajtowe wartości logiczne odpowiadające polom b1, b2, b3, b4. 0 oznacza false, każda inna wartość (domyślnie 1) oznacza true.
  2. 4 4-bajtowe liczby całkowite bez znaku odpowiadające polom u1, u2, u3, u4.
  3. 4 4-bajtowe liczby zmiennoprzecinkowe pojedynczej precyzji odpowiadające polom f1, f2, f3, f4.

Format pliku AKS

To plik binarny przechowujący zapamiętaną tablicę komórek w postaci skompresowanej. Ma budowę podobną do pliku AKN. Rozpoczyna się od nagłówka w postaci 4 znaków:

AKS1

Dalej jest szerokość i wysokość, podobnie jak w pliku AKN. Różni go fakt, że właściwa treść tablicy komórek jest zapisana w postaci strumienia danych skompresowanego za pomocą biblioteki zlib.

Wyświetlanie

Najważniejszą rzeczą, obok obliczeń, jest wyświetlanie wyników. Sposób prezentacji tablicy przez program i dostępne w tym zakresie możliwości są kompromisem pomiędzy elastycznością, a wydajnością.

Background

Obowiązkowa
Typ: kolor

Określa kolor tła.

Przykład:

Background = blue

W tym miejscu jest najwyższy czas, aby opisać sposób zapisywania kolorów w części I pliku AK. Jako koloru można użyć jednej z 16 dostępnych nazw odpowiadającym standardowym kolorom HTML:

Można też używać wartości poszczególnych składowych RGB zapisanych w postaci szesnastkowej, np. FFFFFF, 7FC0FF, 00FFFF itp.

Ustawienia wyświetlania

Dostępne jest kilka parametrów służących do regulacji sposobu wyświetlania tablicy. Można je zmieniać za pomocą odpowiednich kontrolek w czasie działania programu, a ich wartości początkowe można nadawać za pomocą opcjonalnych sekcji I części pliku AK.

Powiększenie

Podstawowym parametrem jest powiększenie. Można go zmieniać w programie za pomocą suwaka Zmień powiększenie. Kolorem żółtym oznaczony jest na tym suwaku zakres, w którym obraz nie zmieści się już w całości na ekranie przy aktualnej rozdzielczości czy wymiarach okna programu.

Suwak powiększenia jest w skali logarytmicznej. Powiększenie można zmieniać w szerokim zakresie. Znaczenie jego wartości jest inne zależnie od sposobu wizualizacji:

  • Jeśli komórki wizualizowane są jako tekstura, 100% oznacza wyświetlenie jej w skali 1:1, czyli każdej komórce odpowiada jeden piksel na ekranie.
  • Jeśli komórki wizualizowane są jako kwadraciki lub wektory, powiększenie 100% oznacza dostosowanie ich wielkości tak, by akurat zmieściły się oknie w obszarze wyświetlania okna programu w maksymalnym rozmiarze. Zmiana powiększenia jest wtedy skokowa - co jeden piksel wielkości komórki.

Do zresetowania powiększenia do wartości 100% służy przycisk Zresetuj powiększenie.

Zoom

Opcjonalna
Typ: liczba rzeczywista
Domyślnie: 1.0

Określa początkowe powiększenie.

Przykład:

Zoom = 10

Wyświetlanie tekstury

Jeśli automat komórkowy wizualizowany jest jako tekstura, dostępne są dwa dodatkowe parametry opisujące sposób jego wyświetlania przez program.

Pierwszy z nich to filtrowanie. Można je włączyć przyciskiem Filtruj teksturę. Jeśli filtrowanie jest włączone, program nakazuje samplerowi karty graficznej zastosowanie filtrowania liniowego. Tekstura wygląda wtedy w powiększeniu na rozmytą, a nie są widoczne duże, kwadratowe piksele. Ustawienie początkowe tej wartości opisuje następująca sekcja:

Filtered

Opcjonalna
Typ: logiczny
Domyślnie: false

Określa, czy tekstura ma być filtrowana.

Przykład:

Filtered = true

Drugim parametrem wyświetlania tekstury jest rozkładanie sąsiadująco. Jeśli włączone, tekstura nie jest wyświetlana w jednej kopii pośrodku okna programu, ale jest powtarzana jedna obok drugiej w poziomie i w pionie wypełniając cały dostępny obszar. Opcję tą można włączyć przyciskiem Rozłóż teksturę sąsiadująco. Ustawienie początkowe tej wartości opisuje następująca sekcja:

Tiled

Opcjonalna
Typ: logiczny
Domyślnie: false

Określa, czy tekstura ma być rozłożona sąsiadująco.

Przykład:

Tiled = true

Tryb graficzny

Program może pracować w oknie lub w trybie pełnoekranowym. Okno można swobodnie przesuwać, skalować, maksymalizować i minimalizować. Aby przełączyć się w tryb pełnoekranowy, należy wybrać odpowiednie parametry (rozdzielczość i częstotliwość odświeżania ekranu) za pomocą suwaka Wybierz rozdzielczość, a następnie nacisnąć przycisk Przełącz na pełny ekran lub z powrotem do okna.

Ponowne naciśnięcie tego przycisku spowoduje powrót do trybu okienkowego.

Wizualizacja

Przystąpię teraz do opisania najważniejszej chyba i najtrudniejszej części całego programu - ustawień wizualizacji Ustawienia te opisuje sekcja pliku AK o skomplikowanej składni:

View

Wymagana

Ta sekcja określa sposób wizualizacji danych. Nie ma chyba innego wyjścia, niż rozpisać jej składnię na poszczególne elementy:

Cała wartość tej sekcji
Zależnie od wybranego trybu wizualizacji jedna z możliwości:
texture <kolor>
Komórki będą wizualizowane jako pojedyncze piksele tekstury, których kolor będzie obliczany w podany sposób. Jeśli wybiera się ten sposób wizualizacji, nie należy używać innych szerokości i wysokości tablicy, niż kwadratowych o boku będącym potęgą dwójki (np. 64x64, 128x128, 256x256). Inaczej mogą być problemy z utworzeniem takiej tekstury, a co za tym idzie z uruchomieniem programu, wyświetlaniem, rozkładaniem sąsiadująco itd. - ogólnie zachowanie jest wtedy niezdefiniowane.
cells <kolor>
Komórki będą wizualizowane jako kwadraciki, których kolor będzie obliczany w podany sposób, przedzielone marginesem o szerokości jednego piksela i kolorze tła.
vectors <wektor>
Komórki będą wizualizowane jako strzałeczki. Można zmieniać ich kolor, wielkość oraz kierunek. Aby były dobrze widoczne, rozmiary tablicy nie powinny być w tym trybie wizualizacji zbyt duże (np. może to być 40x30).
complex <complex>
Komórki w tym trybie będą wizualizowane każda bądź jako kwadracik (jak w cells), bądź jako strzałeczka (jak w vectors), zależnie od wartości jednego z pól komórki typu bool.
complex
Dowolna kolejność cells i vectors, dowolna kolejność false i true, np.:
[
  <b>
  false = cells <kolor>
  true = vectors <wektor>
]
W ten sposób definiuje się pole typu bool, od którego będzie zależał rodzaj wizualizacji każdej komórki i jaka jego wartość logiczna odpowiada jakiemu rodzajowi wizualizacji wraz z jego parametrami.
wektor
Jedna z możliwości:
[ <kolor> x = <wielkość> y = <wielkość> ]
Określa sposób obliczania koloru oraz współrzędnych X i Y wektora (współrzędne kartezjańskie).
[ <kolor> r = <wielkość> alfa = <wielkość> ]
Określa sposób obliczania koloru oraz długości i kąta nachylenia wektora (współrzędne biegunowe).
wielkość
Jedna z możliwości:
default
Użyta zostanie stała wartość domyślna, zależna od kontekstu użycia tej wartości.
<b>
Użyte zostanie podane pole typu bool, którego wartości logiczne będą odpowiadały dwóm możliwym stanom, zależnie od kontekstu użycia tej wartości.
<u> [ <liczba1> .. <liczba2> ]
Użyte zostanie podane pole typu uint, którego wartości będą się mieściły w podanym przedziale liczbowym. Jeśli wartości tego pola będą wychodziły poza ten przedział, zachowanie jest niezdefiniowane (np. mogą zostać ograniczone do minimalnej/maksymalnej wartości albo strzałka może wychodzić poza swoje pole).
<f> [ <liczba1> .. <liczba2> ]
Użyte zostanie podane pole typu float, którego wartości będą się mieściły w podanym przedziale liczbowym. Jeśli wartości tego pola będą wychodziły poza ten przedział, zachowanie jest niezdefiniowane (np. mogą zostać ograniczone do minimalnej/maksymalnej wartości albo strzałka może wychodzić poza swoje pole).
kolor
Jedna z możliwości:
<k>
Użyta zostanie podana, stała wartość koloru.
<b> [ false = <k>, true = <k> ]
Użyty zostanie jeden z dwóch podanych kolorów, zależnie od wartości logicznej podanego pola typu bool.
<b> [ double <k> <k> <k> <k> ]
Użyty zostanie jeden z czterech podanych kolorów, zależnie od wartości podanego pola typu bool w tablicy poprzedniej i aktualnej. Tej opcji wolno używać tylko, kiedy zastosowane jest potrójne buforowanie. Kolejne wartości kolorów odpowiadają odpowiednio:
  1. Wartość poprzednia false, wartość obecna false
  2. Wartość poprzednia false, wartość obecna true
  3. Wartość poprzednia true, wartość obecna false
  4. Wartość poprzednia true, wartość obecna true
<u> [ RGB ]
Użyte zostanie podane pole typu uint interpretowane bezpośrednio jako wartość koloru XRGB. Najstarszy bajt jest ignorowany, pozostałe odpowiadają poszczególnym składowym koloru.
<u> [ list <liczba> <k> <liczba> <k> ... ]
Użyty zostanie jeden z kolorów odpowiadających podanym, dyskretnym wartościom podanego pola typu uint. Ta opcja przydatna jest podczas wykorzystywania takiego pola jako wartości wyliczeniowej - przyjmującej jedną z kilku wartości. Jeśli wartości nie ma na liście, zachowanie jest niezdefiniowane.
<u> [ gradient <liczba> = <k> .. <liczba> = <k> ]
<f> [ gradient <liczba> = <k> .. <liczba> = <k> ]
Użyty zostanie jeden z kolorów pośrednich pomiędzy pierwszym, a drugim z podanych, zależnie proporcjonalnie od wartości podanego pola spomiędzy pierwszej, a drugiej podanej wartości liczbowej.
<u> [ rainbow <liczba> .. <liczba> ]
<f> [ rainbow <liczba> .. <liczba> ]
Użyty zostanie jeden z kolorów tęczy, zależnie proporcjonalnie od wartości podanego pola spomiędzy pierwszej, a drugiej podanej wartości liczbowej.
b
Nazwa jednego ze składników komórki typu bool - b1, b2, b3, b4.
u
Nazwa jednego ze składników komórki typu uint - u1, u2, u3, u4.
f
Nazwa jednego ze składników komórki typu float - f1, f2, f3, f4.
k
Wartość koloru - jedna z 16 dozwolonych nazw lub liczba szesnastkowa, tak jak zostało to opisane wyżej.
liczba
Wartość liczbowa, zależnie od kontekstu całkowita lub rzeczywista.

Przykłady:

View = cells b1 [ false = 606060, true = white ]

Komórki wizualizowane będą jako kwadraciki o kolorze zależnym od pola b1. Jeśli w danej komórce to pole ma wartość false, do jej narysowania użyty zostanie kolor ciemny szary. Jeśli ma wartość true, użyty zostanie kolor biały.

View = texture u1 [ RGB ]

Tablica rysowana będzie w postaci tekstury, a kolory jej pikseli będą odpowiadały wartościom liczbowym pól u1 w poszczególnych komórkach interpretowanym bezpośrednio jako wartość liczbowa koloru.

View = complex [
  b1
  true = cells
    silver
  false = vectors [
    f1 [ rainbow 0.0 .. 1.0 ]
    x = f2 [ -1.0 .. +1.0 ]
    y = f3 [ -1.0 .. +1.0 ]
  ]
]

Jeśli dana komórka ma pole b1 równe true, przedstawiana będzie jako kwadracik w kolorze jasnym szarym. Jeśli ma to pole równe false, przedstawiana będzie jako strzałeczka, której kolor ma być kolorem tęczy zależnym od pola f1 (które przyjmuje wartości z zakresu od 0.0 - tej wartości będzie odpowiadał kolor czerwony, do 1.0 - tej wartości będzie odpowiadał kolor niebieski), a kierunek wektora opisują współrzędne: X podana w polu f2 i Y podana w polu f3.

Funkcje do kolorów

Choć program został tak skonstruowany, aby skrypt mógł po prostu operować na wartościach pól komórek, a sposób ich rysowania opisywała wyłącznie wizualizacja, jej możliwości mogą okazać się niewystarczające. Wówczas rozwiązaniem jest obliczanie koloru do rysowania komórek również za pomocą skryptu. Do pomocy przychodzą wtedy funkcje do operacji na kolorach:

uint InterpolateColor(uint, uint, float)

Zwraca kolor zinterpolowany pomiędzy dwoma podanymi, zależnie od wartości liczbowej trzeciego parametru z zakresu od 0.0 do 1.0.

Najstarszy bajt zwracanego koloru oficjalnie jest niezdefiniowany. Nieoficjalnie, w obecnej wersji jest zawsze równy 0xFF :)

Przykład:

InterpolateColor(0x000000, 0xFFFFFF, 0.5)

Zwraca kolor szary równo w połowie między czarnym, a białym.

uint RainbowColor(float)

Zwraca jeden z kolorów tęczy zależnie od podanej wartości liczbowej z zakresu od 0.0 do 1.0.

Najstarszy bajt zwracanego koloru oficjalnie jest niezdefiniowany. Nieoficjalnie, w obecnej wersji jest zawsze równy 0x00 :)

float Grayscale(uint)

Zwraca wartość z zakresu od 0.0 do 1.0 po przetworzeniu podanego koloru na odcień szarości.

Przykład:

SetBuffer_b1(x, y, Grayscale(GetBuffer_u1(x, y)) >= 0.5);

Obsługa obrazków

Ponieważ automaty komórkowe można stosować do generowania i przetwarzania grafiki, program wyposażony został w funkcje wczytywania i zapisywania obrazków w wielu formatach graficznych. Trzeba uczciwie przyznać, że nie wszystkie działają poprawnie. Częściowo jest to moja wina, ale częściowo też wina użytej biblioteki DevIL. Jednak te podstawowe - BMP, JPG, PNG, GIF itp. powinny raczej działać :)

Do wczytania obrazka służy polecenie Wczytaj obrazek, a do zapisania - Zapisz obrazek. Polecenia ta działają wyłącznie w trybie okienkowym (nie na pełnym ekranie). Pokazują standardowe okna dialogowe Windows do wyboru nazwy pliku i pozwalają wybrać jeden z wielu dostępnych formatów graficznych (więcej jest obsługiwanych podczas wczytywania, niż podczas zapisywania).

Wczytywanie i zapisywanie obrazków odbywa się w sposób przezroczysty tak, że skrypt nie musi mieć z tym niczego wspólnego. Jeśli wymiary obrazka z wczytywanego pliku nie odpowiadają wymiarom tablicy automatu wykonywanego aktualnie w programie, piksele spoza zakresu zostaną zignorowane, a piksele niewczytane z pliku pozostawią odpowiadające im komórki bez zmian.

Kolory pikseli obrazka są zapisywane z i wczytywane do składowej u1 komórek tablicy. Jeśli w swoim automacie komórkowym używasz innych pól i chcesz zrobić możliwość obsługi obrazków, musisz:

  1. Nie używać pola u1.
  2. Zaimplementować w skrypcie funkcje OnLoadImage() i OnSaveImage().

void OnLoadImage()

Opcjonalnie udostępniana przez skrypt dla programu

Wywoływana jest po wczytaniu z obrazka.

Przykład:

Kolory pikseli są wczytywane z obrazka do u1. Jeśli w automacie komórkowym używamy wartości logicznej pola b1, musimy po wczytaniu obrazka przepisać wartości kolorów konwertując je na wartości logiczne do tego używanego pola, np. na podstawie pewnego progu jasności danego piksela:

void OnLoadImage()
{
 int cx = GetWidth(), cy = GetHeight(), x, y;
  for (y = 0; y < cy; y++) {
    for (x = 0; x < cx; x++) {
      SetBuffer_b1(x, y, Grayscale(GetBuffer_u1(x, y)) >= 0.5);
    }
  }
}

void OnSaveImage()

Opcjonalnie udostępniana przez skrypt dla programu

Wywoływana jest przed zapisaniem do obrazka.

Przykład:

Kolory pikseli są zapisywane do obrazka z u1. Jeśli w automacie komórkowym używamy wartości logicznej pola b1, musimy przed zapisaniem obrazka przepisać wartości logiczne do tego pola konwertując je na kolory, np. jeden z dwóch kolorów:

void OnSaveImage()
{
  int cx = GetWidth(), cy = GetHeight(), x, y;
  for (y = 0; y < cy; y++) {
    for (x = 0; x < cx; x++) {
      if (GetBuffer_b1(x, y))
        SetBuffer_u1(x, y, 0xFFFFFF);
      else
        SetBuffer_u1(x, y, 0);
    }
  }
}

Własne narzędzia

Oprócz uruchamiania, zatrzymywania i regulowania prędkości wykonywania automatu komórkowego, użytkownik programu może wchodzić z nim w interakcje za pomocą narzędzi przygotowanych przez autora pliku AK. Narzędzia te mają postać dodatkowych przycisków oraz obsługi zdarzeń od myszki.

Przyciski

Autor pliku AK może oddać do dyspozycji dla użytkownika programu dodatkowe narzędzia w postaci przycisków obsługiwanych przez skrypt, których działanie dostosowane będzie do konkretnego automatu komórkowego. Przyciski te wyświetlane są po lewej stronie na pionowym pasku i nie mają ikon.

Aby stworzyć przyciski, należy przypisać im etykiety tekstowe za pomocą sekcji:

Buttons

Opcjonalna
Struktura: [ <numer> = <etykieta>, <numer> = <etykieta>, ... ]

Określa, które przyciski mają być widoczne i nadaje im etykiety tekstowe.

Przykład:

Buttons = [
  1 = "Wyczyść"
  2 = "Zmień 10%"
  3 = "Wypełnij 50%"

  4 = "Rysuj 0"
  5 = "Rysuj 1"

  7 = "Linia"
  8 = "Prostokąt"
]

Dostępnych przycisków jest 10 i są numerowane w sposób następujący: 1, 2, 3, 4, 5, 6, 7, 8, 9, 0. Aby obsłużyć ich naciśnięcie, należy napisać w skrypcie wybrane z tych funkcji:

void OnButton1()
void OnButton2()
void OnButton3()
void OnButton4()
void OnButton5()
void OnButton6()
void OnButton7()
void OnButton8()
void OnButton9()
void OnButton0()

Opcjonalnie udostępniane przez skrypt dla programu

Wywoływane są po naciśnięciu odpowiedniego przycisku za pomocą myszki lub skrótu klawiszowego.

Przyciski mogą być wciśnięte lub niewciśnięte. Stanem tym można sterować za pomocą skryptu. Nie ma on żadnego wypływu na możliwość ich naciskania. Służy tylko do sygnalizacji wyboru użytkownika - grupa przycisków może służyć jako przełącznik pewnej wartości wyliczeniowej.

bool GetButtonDown(int)
void SetButtonDown(int, bool)

Zwraca lub ustawia stan wciśnięcia przycisku o podanym numerze.

Przykład:

bool CzymRysowac;

void OnButton4()
{  
  CzymRysowac = false;
  SetButtonDown(4, true);
  SetButtonDown(5, false);
}

void OnButton5()
{
  CzymRysowac = true;
  SetButtonDown(4, false);
  SetButtonDown(5, true);
}

Obsługa myszki

Uzupełnieniem narzędzi użytkownika jest możliwość obsługiwania zdarzeń od myszki. Za ich pomocą można tworzyć nawet zaawansowane narzędzia do interakcji z automatem komórkowym, na przykład do rysowania na tablicy punktów, linii czy prostokątów poprzez dowolne zmienianie stanu wskazywanych komórek.

void OnMouseDown(const int, const int)

Opcjonalnie udostępniana przez skrypt dla programu

Wywoływana jest po wciśnięciu przez użytkownika lewego przycisku myszki nad obszarem tablicy. Przekazane zostają współrzędne wskazanej komórki tablicy.

void OnMouseUp(const int, const int)

Opcjonalnie udostępniana przez skrypt dla programu

Wywoływana jest po zwolnieniu przez użytkownika lewego przycisku myszki nad obszarem tablicy. Przekazane zostają współrzędne wskazanej komórki tablicy.

void OnMouseMove(const int, const int)

Opcjonalnie udostępniana przez skrypt dla programu

Wywoływana jest po przesunięciu kursora myszy nad obszarem tablicy, ale tylko, jeśli lewy przycisk myszy jest wciśnięty. Przekazane zostają współrzędne wskazywanej komórki tablicy.

Przykład:

int LastX, LastY;

void OnMouseDown(const int x, const int y)
{
  // Zapamiętaj pozycję początkową
  LastX = x; LastY = y;
}

void OnMouseUp(const int x, const int y)
{
  // Narysuj prostokąt
  int ix, iy;
  int x1 = min_i(x, LastX), x2 = max_i(x, LastX);
  int y1 = min_i(y, LastY), y2 = max_i(y, LastY);
  for (iy = y1; iy <= y2; iy++) {
    for (ix = x1; ix <= x2; ix++) {
      SetBuffer_b1(ix, iy, true);
    }
  }
}

Dodatki

Zastanawiałem się nad strukturą tej dokumentacji i ostateczne zdecydowałem się zorganizować ją tak, jak zorganizowane są np. specyfikacje standardów HTML czy CSS wydane przez konsorcjum W3C. Dokument podzielony został tematycznie, a opisy poszczególnych elementów są porozrzucane po różnych jego miejscach związanych z danym tematem. Dlatego tekst uzupełniać musi systematyczny spis wszystkich takich elementów wraz z odsyłaczami do ich opisów. Oto on, w kolejności alfabetycznej:

Indeks sekcji części I pliku AK

BackgroundObowiązkowa
BuffersObowiązkowa
ButtonsOpcjonalna
DefaultOpcjonalna
FilteredOpcjonalna
HeightObowiązkowa
RunningOpcjonalna
SpeedOpcjonalna
TiledOpcjonalna
TitleObowiązkowa
ViewObowiązkowa
WidthObowiązkowa
WrapObowiązkowa
ZoomOpcjonalna

Indeks funkcji części II pliku AK

Funkcje udostępniane przez skrypt dla programu

void OnButton0()Opcjonalna
void OnButton1()Opcjonalna
void OnButton2()Opcjonalna
void OnButton3()Opcjonalna
void OnButton4()Opcjonalna
void OnButton5()Opcjonalna
void OnButton6()Opcjonalna
void OnButton7()Opcjonalna
void OnButton8()Opcjonalna
void OnButton9()Opcjonalna
void OnFrame()Obowiązkowa
void OnInit()Obowiązkowa
void OnLoadImage()Opcjonalna
void OnMouseDown(const int, const int)Opcjonalna
void OnMouseMove(const int, const int)Opcjonalna
void OnMouseUp(const int, const int)Opcjonalna
void OnSaveImage()Opcjonalna

Funkcje udostępniane przez program dla skryptu

float abs_f(float)
int abs_i(int)
float acos(float)
float asin(float)
void assert(bool)
float atan(float)
float atan2(float, float)
void Beep(uint, uint)
float ceil(float)
float cos(float)
float cosh(float)
void Debug(int, int)
float E()
void error(int)
float exp(float)
float fmod(float)
float floor(float)
bool GetBuffer_b1(int, int)
bool GetBuffer_b2(int, int)
bool GetBuffer_b3(int, int)
bool GetBuffer_b4(int, int)
float GetBuffer_f1(int, int)
float GetBuffer_f2(int, int)
float GetBuffer_f3(int, int)
float GetBuffer_f4(int, int)
uint GetBuffer_u1(int, int)
uint GetBuffer_u2(int, int)
uint GetBuffer_u3(int, int)
uint GetBuffer_u4(int, int)
bool GetButtonDown(int)
int GetHeight()
int GetWidth()
float Grayscale(uint)
uint InterpolateColor(uint, uint, float)
float log10(float)
float log(float)
int max_i(int, int)
float max_f(float, float)
uint max_u(uint, uint)
int min_i(int, int)
float min_f(float, float)
uint min_u(uint, uint)
int minmax_i(int, int, int)
float minmax_f(float, float, float)
uint minmax_u(uint, uint, uint)
float PI()
float pow(float, float)
uint RainbowColor(float)
bool rand_b()
float rand_f()
float rand_f_range(float, float)
float rand_normal(float)
uint rand_u()
uint rand_u_range(uint, uint)
int round(float)
void SetBuffer_b1(int, int, bool)
void SetBuffer_b2(int, int, bool)
void SetBuffer_b3(int, int, bool)
void SetBuffer_b4(int, int, bool)
void SetBuffer_f1(int, int, float)
void SetBuffer_f2(int, int, float)
void SetBuffer_f3(int, int, float)
void SetBuffer_f4(int, int, bfloatool)
void SetBuffer_u1(int, int, uint)
void SetBuffer_u2(int, int, uint)
void SetBuffer_u3(int, int, uint)
void SetBuffer_u4(int, int, uint)
void SetButtonDown(int, bool)
float sin(float)
float sinh(float)
float sqrt(float)
float tan(float)
float tanh(float)

Indeks dokumentacji formatów plików

Format pliku AK
Format pliku AKN
Format pliku AKS

Indeks poleceń interfejsu użytkownika

Filtruj teksturę
Przełącz na pełny ekran lub z powrotem do okna
Rozłóż teksturę sąsiadująco
Uruchom
Wczytaj obrazek
Wczytaj tablicę
Wybierz rozdzielczość
Wykonaj jeden krok
Wykonaj daną liczbę iteracji
Wyzeruj licznik iteracji
Zakończ działanie programu
Zapisz obrazek
Zapisz tablicę
Zatrzymaj
Zmień powiększenie
Zmień prędkość
Zresetuj powiększenie